﻿using System;
using System.IO;
using System.Windows;
using FirstFloor.Documents.Imaging;
using FirstFloor.Documents.IO;
using FirstFloor.Documents.Pdf.Fonts;

namespace FirstFloor.Documents.Pdf
{
    /// <summary>
    /// Implements a package reader capable of parsing and converting PDF documents to XPS.
    /// </summary>
    public sealed class PdfDocumentReader
        : MemoryPackageReader
    {
        private PdfDocument document;

        /// <summary>
        /// Initializes a new instance of the <see cref="PdfDocumentReader"/> class.
        /// </summary>
        /// <param name="resourceUri">The resource URI.</param>
        public PdfDocumentReader(Uri resourceUri)
        {
            if (resourceUri == null) {
                throw new ArgumentNullException("resourceUri");
            }
            var streamInfo = Application.GetResourceStream(resourceUri);
            if (streamInfo == null) {
                throw new ArgumentException(string.Format(Resources.ResourceNotFound, resourceUri));
            }

            Initialize(streamInfo.Stream);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PdfDocumentReader"/> class.
        /// </summary>
        /// <param name="stream">The stream.</param>
        public PdfDocumentReader(Stream stream)
        {
            Initialize(stream);
        }

        private void Initialize(Stream stream)
        {
            if (stream == null) {
                throw new ArgumentNullException("stream");
            }

            // initialize system typeface on the UI thread
            UISynchronizationContext.Current.EnsureUIThread();
#if !PHONE
            StandardFonts.Initialize();
#endif

            this.document = PdfParser.ParseDocument(stream);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public override void Dispose()
        {
            this.document.Dispose();
        }

        /// <summary>
        /// Gets the number of pages.
        /// </summary>
        /// <value>The page count.</value>
        public override int PageCount
        {
            get { return this.document.PageCount; }
        }

        /// <summary>
        /// Gets the XAML of specified page.
        /// </summary>
        /// <param name="pageNumber">The page number.</param>
        /// <returns>The page XAML.</returns>
        public override string GetPageXaml(int pageNumber)
        {
            return this.document.GetPageXaml(pageNumber, LoadSettings.Current);
        }

        /// <summary>
        /// Gets the part identified with given uri.
        /// </summary>
        /// <param name="partUri">The part URI.</param>
        /// <returns>The part stream.</returns>
        public override Stream GetPart(string partUri)
        {
            if (partUri == null) {
                throw new ArgumentNullException("partUri");
            }

            // part should be either an image or a font
            ObjectIdentifier id;
            if (RequestHelper.TryParseFontIdentifier(partUri, out id)) {
                return GetFontStream(id);
            }
            if (RequestHelper.TryParseImageIdentifier(partUri, out id)) {
                return GetImageStream(id);
            }

            return null;    // part not found
        }

        private Stream GetFontStream(ObjectIdentifier identifier)
        {
            var font = this.document.Owner.GetFont(identifier);

            if (font != null) {
                return font.GetFontFile();
            }
            return null;
        }

        private Stream GetImageStream(ObjectIdentifier identifier)
        {
            var parser = this.document.Owner;
            var dictionary = parser.ParseIndirectObject<DictionaryObject>(identifier);
            if (dictionary != null) {
                var stream = parser.ParseStreamObject(dictionary);

                if (stream != null) {
                    try {
                        return Images.PdfImageDecoder.GetImageStream(stream);
                    }
                    catch (Exception) {
                        // too bad, image decoding failed
                    }
                }
            }

            // sorry, image not supported
            return MemoryStream.Null;
        }

        /// <summary>
        /// Determines whether the specified stream contains a PDF document.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns>
        /// 	<c>true</c> if specified stream contains a PDF document; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsPdfStream(Stream stream)
        {
            stream.VerifyReadAndSeek();
            stream.Seek(0, SeekOrigin.Begin);
            return stream.IsMatch('%', 'P', 'D', 'F', '-');
        }
    }
}
